package NEHE.JOGL.Port;

import java.awt.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.geom.Rectangle2D;
import java.text.DecimalFormat;
import javax.media.opengl.GL;
import javax.media.opengl.GLAutoDrawable;
import javax.media.opengl.GLCanvas;
import javax.media.opengl.GLEventListener;
import javax.media.opengl.glu.GLU;
import javax.swing.*;
import com.sun.opengl.util.FPSAnimator;
import com.sun.opengl.util.j2d.TextRenderer;
import static javax.media.opengl.GL.*;

/**
 * NeHe Lesson 14: 3D Text Rendering using TextRenderer
 */
public class NeheJOGL14Text3D extends JPanel implements GLEventListener {
   private static final int REFRESH_FPS = 60;    // Display refresh frames per second
   private GLU glu;             // For the GL Utility
   final FPSAnimator animator;  // Used to drive display() 
   GLCanvas canvas;
   
   private TextRenderer textRenderer;
   private String msg = "NeHe - ";
   private DecimalFormat formatter = new DecimalFormat("###0.00");

   private float textPosX; // x-position of the 3D text
   private float textPosY; // y-position of the 3D text
   private float textScaling; // scaling factor for 3D text

   private static float rotateAngle = 0.0f;

   // Constructor
   public NeheJOGL14Text3D() {
      canvas = new GLCanvas();
      this.setLayout(new BorderLayout());
      this.add(canvas, BorderLayout.CENTER);
      canvas.addGLEventListener(this);
      //canvas.addKeyListener(this);
      //canvas.setFocusable(true);  // To receive key event
      //canvas.requestFocus();
   
      // Run the animation loop using the fixed-rate Frame-per-second animator,
      // which calls back display() at this fixed-rate (FPS).
      animator = new FPSAnimator(canvas, REFRESH_FPS, true);
   }

   // Main program
   public static void main(String[] args) {
      final int WINDOW_WIDTH = 320;
      final int WINDOW_HEIGHT = 240;
      final String WINDOW_TITLE = "Nehe #14: Outline Fonts";

      JFrame frame = new JFrame();
      final NeheJOGL14Text3D joglMain = new NeheJOGL14Text3D();
      frame.setContentPane(joglMain);
      frame.addWindowListener(new WindowAdapter() {
         @Override 
         public void windowClosing(WindowEvent e) {
            // Use a dedicate thread to run the stop() to ensure that the
            // animator stops before program exits.
            new Thread() {
               @Override 
               public void run() {
                  joglMain.animator.stop(); // stop the animator loop
                  System.exit(0);
               }
            }.start();
         }
      });
      frame.setSize(WINDOW_WIDTH, WINDOW_HEIGHT);
      frame.setTitle(WINDOW_TITLE);
      frame.setVisible(true);
      joglMain.animator.start(); // start the animation loop
   }

   // ------ Implement methods declared in GLEventListener ------

   /**
    * Called by the drawable immediately after the OpenGL context is initialized for
    * the first time. Can be used to perform one-time OpenGL initialization such as
    * setup of lights and display lists. Run only once.
    */
   @Override
   public void init(GLAutoDrawable drawable) {
      // Get the OpenGL graphics context
      GL gl = drawable.getGL();
      // GL Utilities
      glu = new GLU();
      // Enable smooth shading, which blends colors nicely across a polygon, and
      // smoothes out lighting.
      gl.glShadeModel(GL_SMOOTH);
      // Set background color (in RGBA).
      gl.glClearColor(0.0f, 0.0f, 0.0f, 0.5f);
      // Setup the depth buffer & enable the depth testing
      gl.glClearDepth(1.0f);
      gl.glEnable(GL_DEPTH_TEST); // enables depth testing
      gl.glDepthFunc(GL_LEQUAL); // the type of depth test to do
      // We want the best perspective correction to be done
      gl.glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);

      gl.glEnable(GL_LIGHT0); // Enable default light (quick and dirty)
      gl.glEnable(GL_LIGHTING); // Enable lighting
      gl.glEnable(GL_COLOR_MATERIAL); // Enable coloring of material

      // Allocate textRenderer with the chosen font
      textRenderer = new TextRenderer(new Font("SansSerif", Font.BOLD, 12));

      // Calculate the position and scaling factor
      Rectangle2D bounds = textRenderer.getBounds(msg + "00.00");
      int textWidth = (int)bounds.getWidth();
      int textHeight = (int)bounds.getHeight();
      // System.out.println("w = " + textWidth);
      // System.out.println("h = " + textHeight);
      // 104 x 14
   }

   /**
    * Called by the drawable to initiate OpenGL rendering.
    */
   @Override
   public void display(GLAutoDrawable drawable) {
      // Get the OpenGL graphics context
      GL gl = drawable.getGL();
      // Clear the screen and the depth buffer
      gl.glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
      // Reset the view (x, y, z axes back to normal)
      gl.glLoadIdentity();

      // ----- Rendering 3D text using TextRenderer class -----

      textRenderer.begin3DRendering();

      gl.glTranslatef(0.0f, 0.0f, -50.0f);
      gl.glRotatef(rotateAngle, 1.0f, 0.0f, 0.0f);
      gl.glRotatef(rotateAngle * 1.5f, 0.0f, 1.0f, 0.0f);
      gl.glRotatef(rotateAngle * 1.4f, 0.0f, 0.0f, 1.0f);

      // Pulsing Colors Based On Text Position
      textRenderer.setColor((float)(Math.cos(rotateAngle / 20.0f)), // r
            (float)(Math.sin(rotateAngle / 25.0f)), // g
            1.0f - 0.5f * (float)(Math.cos(rotateAngle / 17.0f)), 0.5f); // b

      // String, x, y, z, and scaling - need to scale down
      // Not too sure how to compute the x, y and scaling - trial and error!
      textRenderer.draw3D(msg + formatter.format(rotateAngle / 50), -20.0f,
            0.0f, 0.0f, 0.4f);

      // Clean up rendering
      textRenderer.end3DRendering();

      // Update the rotate angle
      rotateAngle += 0.1f;
   }

   /**
    * Called by the drawable during the first repaint after the component has been
    * resized. Run at least once when the window is first set to visible.
    */
   @Override
   public void reshape(GLAutoDrawable drawable, int x, int y, int width, int height) {
      // Get the OpenGL graphics context
      GL gl = drawable.getGL();

      if (height == 0) {
         height = 1; // prevent divide by zero
      }
      float aspect = (float)width / height;

      // Set the viewport (display area) to cover the entire window
      gl.glViewport(0, 0, width, height);

      // Enable perspective view (where far objects are smaller)
      gl.glMatrixMode(GL_PROJECTION);
      gl.glLoadIdentity(); // reset
      // View angle of 45 degrees, based on this canvas's aspect ratio. The 0.1f,
      // 100.0f is the starting and ending points for how deep we can draw into
      // the screen.
      glu.gluPerspective(45.0f, aspect, 0.1f, 100.0f); // fovy, aspect, zNear, zFar

      // Enable the model view - any new transformations will affect the model-view
      // matrix
      gl.glMatrixMode(GL_MODELVIEW);
      gl.glLoadIdentity(); // reset
   }

   /**
    * Called when the display mode (eg. resolution) has been changed.
    */
   @Override
   public void displayChanged(GLAutoDrawable drawable, boolean modeChanged,
         boolean deviceChanged) {}
}
